﻿using System;
using System.Collections.Generic;
using OhioState.Math;

namespace OhioState.Graphics
{
    /// <summary>
    /// Camera Helper Class for calculating MV matrix and the coordinate frame
    /// </summary>
    public static class CameraHelper
    {
        /// <summary>
        /// Computes the ModelView Matrix
        /// </summary>
        /// <param name="position">position</param>
        /// <param name="lookAtDirection">lookAt direction</param>
        /// <param name="viewUp">Up direction</param>
        /// <returns>MV matrix</returns>
        public static Matrix4 ComputeModelViewMatrix(Vector3 position, Vector3 lookAtDirection, Vector3 viewUp)
        {
            Vector3 n = new Vector3(lookAtDirection);
            Vector3 u = viewUp.cross(lookAtDirection);
            n.normalize();
            u.normalize();
            Vector3 v = n.cross(u);
            Matrix4 modelView = new Matrix4();
            modelView.Identity();
            modelView[0, 0] = u.x; modelView[0, 1] = u.y; modelView[0, 2] = u.z; modelView[0, 3] = -position.dot(u);
            modelView[1, 0] = v.x; modelView[1, 1] = v.y; modelView[1, 2] = v.z; modelView[1, 3] = -position.dot(v);
            modelView[2, 0] = n.x; modelView[2, 1] = n.y; modelView[2, 2] = n.z; modelView[2, 3] = -position.dot(n);

            return modelView;
        }

        /// <summary>
        /// Extracts the coordinate frame by inverting model view matrix and if invertible calculates the various parameters
        /// </summary>
        /// <param name="camera">Input Camera</param>
        /// <param name="position">position</param>
        /// <param name="lookAtDirection">lookAtDirection</param>
        /// <param name="viewUp">Up direction</param>
        /// <param name="viewAngle">View angle</param>
        public static void ExtractCoordinateFrame(ICamera camera, out Vector4 position, out Vector4 lookAtDirection, out Vector4 viewUp, out float viewAngle)
        {
            Matrix4 modelView = camera.GetModelViewMatrix();
            Matrix4 projection = camera.GetProjectionMatrix();
            position = modelView.getColVector(3);
            lookAtDirection = modelView.getRowVector(2);
            viewUp = modelView.getRowVector(1);
            Matrix4 modelInverse = new Matrix4(modelView);
            Matrix4 projectionInverse = new Matrix4(projection);
            viewAngle = 45.0f;
            if (Matrix4.safe_inverse(modelInverse))
            {
                position = modelInverse * new Vector4(0, 0, 0, 1);
            }
            else
                throw new ApplicationException("Model View is not invertible");

            if (Matrix4.safe_inverse(projectionInverse))
            {
                //
                // Compute the view angle. 
                //
                Vector4 topNear = projectionInverse * new Vector4(0, 1, 0, 1);
                viewAngle = (float) (180.0 * System.Math.Abs(System.Math.Atan2(topNear.y, -topNear.z)) / System.Math.PI);
            }
        }
    }
}
